﻿/**
<code>
  <author name="Zealic" email="zealic(at)gmail.com" type="Creator"/>
</code>
**/
using System;
using System.Globalization;

namespace Zealib.Numeric
{
    public struct BigInteger
    {
        #region Fields
        private const short MAGIC = 0x2010;
        private const short SIGN_ZERO = 0;
        private const short SIGN_PLUS = 1;
        private const short SIGN_NEGATIVE = -1;
        private short _Sign;
        private uint[] m_Data;


        #endregion

        #region Constuctor
        public BigInteger(int value)
        {
            if (value == 0)
            {
                _Sign = 0;
                m_Data = new uint[0];
            }
            else if (value < 0)
            {
                _Sign = -1;
                m_Data = new[] { (uint)-value };
            }
            else
            {
                _Sign = 1;
                m_Data = new[] { (uint)value };
            }
        }

        public BigInteger(uint value)
        {
            if (value == 0)
            {
                _Sign = 0;
            }
            else
            {
                _Sign = 1;
            }
            m_Data = new[] { value };
        }

        public BigInteger(long value)
        {
            ulong num = 0L;
            if (value < 0L)
            {
                num = (ulong)-value;
                _Sign = -1;
            }
            else if (value > 0L)
            {
                num = (ulong)value;
                _Sign = 1;
            }
            else
            {
                _Sign = 0;
            }
            if (num >= 0x100000000L)
            {
                m_Data = new[] { (uint)num, (uint)(num >> 0x20) };
            }
            else
            {
                m_Data = new[] { (uint)num };
            }
        }


        public BigInteger(ulong value)
        {
            _Sign = (short)(value == 0L ? 0 : 1);
            if (value >= 0x100000000L)
            {
                m_Data = new[] { (uint)value, (uint)(value >> 0x20) };
            }
            else
            {
                m_Data = new[] { (uint)value };
            }
        }

        public BigInteger(float value)
            : this((double)value)
        {
        }

        public BigInteger(double value)
            : this((decimal)value)
        {
        }

        public BigInteger(decimal value)
        {
            _Sign = (short)(value == 0L ? 0 : 1);
            var strValue = decimal.Truncate(value).ToString();
            m_Data = ExtractInteger(strValue);
        }

        internal BigInteger(short sign, uint[] data)
        {
            _Sign = sign;
            m_Data = new uint[data.Length];
            Buffer.BlockCopy(data, 0, m_Data, 0, m_Data.Length * 4);
        }

        #endregion

        #region Helper
        private static uint[] ExtractInteger(string value)
        {
            if (value.Contains("."))
                value = value.Substring(value.IndexOf('.') + 1);
            throw new NotImplementedException();
        }
        #endregion

        #region Public members
        #region Operator
        public static BigInteger operator +(BigInteger value)
        {
            return value;
        }

        public static BigInteger operator -(BigInteger value)
        {
            return Negate(value);
        }

        public static BigInteger operator +(BigInteger x, BigInteger y)
        {
            return Add(x, y);
        }

        public static BigInteger operator -(BigInteger x, BigInteger y)
        {
            return Subtract(x, y);
        }

        public static BigInteger operator *(BigInteger x, BigInteger y)
        {
            return Multiply(x, y);
        }

        public static BigInteger operator /(BigInteger x, BigInteger y)
        {
            return Divide(x, y);
        }

        public static BigInteger operator %(BigInteger x, BigInteger y)
        {
            BigInteger result;
            DivRem(x, y, out result);
            return result;
        }

        public static bool operator >(BigInteger x, BigInteger y)
        {
            return GreaterThan(x, y);
        }

        public static bool operator <(BigInteger x, BigInteger y)
        {
            return LessThan(x, y);
        }

        public static bool operator >=(BigInteger x, BigInteger y)
        {
            return GreaterThanOrEqual(x, y);
        }

        public static bool operator <=(BigInteger x, BigInteger y)
        {
            return LessThanOrEqual(x, y);
        }

        public static bool operator ==(BigInteger x, BigInteger y)
        {
            return Equals(x, y);
        }

        public static bool operator !=(BigInteger x, BigInteger y)
        {
            return !Equals(x, y);
        }

        public static implicit operator BigInteger(byte value)
        {
            return new BigInteger((int)value);
        }

        public static implicit operator BigInteger(sbyte value)
        {
            return new BigInteger((uint)value);
        }

        public static implicit operator BigInteger(short value)
        {
            return new BigInteger(value);
        }

        public static implicit operator BigInteger(ushort value)
        {
            return new BigInteger(value);
        }

        public static implicit operator BigInteger(int value)
        {
            return new BigInteger(value);
        }

        public static implicit operator BigInteger(uint value)
        {
            return new BigInteger(value);
        }

        public static implicit operator BigInteger(long value)
        {
            return new BigInteger(value);
        }

        public static implicit operator BigInteger(ulong value)
        {
            return new BigInteger(value);
        }

        public static implicit operator BigInteger(float value)
        {
            return new BigInteger(value);
        }

        public static implicit operator BigInteger(double value)
        {
            return new BigInteger(value);
        }

        public static implicit operator BigInteger(decimal value)
        {
            return new BigInteger(value);
        }
        #endregion

        #region Function implement
        public static BigInteger Abs(BigInteger value)
        {
            throw new NotImplementedException();
        }

        private static uint[] InternalAdd(uint[] xData, int xLen, uint[] yData, int yLen)
        {
            uint[] result = new uint[xLen];
            ulong currentValue = 0L;
            int offset = 0;
            while (offset < yLen)
            {
                currentValue = (currentValue + xData[offset]) + yData[offset];
                result[offset] = (uint)currentValue;
                currentValue = currentValue >> 0x20;
                offset++;
            }
            while ((offset < xLen) && (currentValue != 0L))
            {
                currentValue += xData[offset];
                result[offset] = (uint)currentValue;
                currentValue = currentValue >> 0x20;
                offset++;
            }
            if (currentValue == 0L)
            {
                while (offset < xLen)
                {
                    result[offset] = xData[offset];
                    offset++;
                }
                return result;
            }
            result = NumericHelper.Resize(result, xLen + 1);
            result[offset] = (uint)currentValue;
            return result;
        }

        public static BigInteger Add(BigInteger x, BigInteger y)
        {
            if (x._Sign == y._Sign)
            {
                return new BigInteger(x._Sign, InternalAdd(x.m_Data, x.Length, y.m_Data, y.Length));
            }
            return (x - (-y));
        }

        public static BigInteger Subtract(BigInteger x, BigInteger y)
        {
            throw new NotImplementedException();
        }

        public static BigInteger Multiply(BigInteger x, BigInteger y)
        {
            throw new NotImplementedException();
        }

        public static BigInteger Divide(BigInteger dividend, BigInteger divisor)
        {
            throw new NotImplementedException();
        }

        public static BigInteger DivRem(BigInteger dividend, BigInteger divisor, out BigInteger remainder)
        {
            throw new NotImplementedException();
        }

        public static BigInteger Remainder(BigInteger dividend, BigInteger divisor)
        {
            BigInteger result;
            DivRem(dividend, divisor, out result);
            return result;
        }

        public static BigInteger Pow(BigInteger baseValue, BigInteger exponent)
        {
            throw new NotImplementedException();
        }

        public static BigInteger ModPow(BigInteger baseValue, BigInteger exponent, BigInteger modulus)
        {
            throw new NotImplementedException();
        }

        public static BigInteger Negate(BigInteger value)
        {
            return new BigInteger();
        }


        public static bool LessThan(BigInteger x, BigInteger y)
        {
            throw new NotImplementedException();
        }

        public static bool LessThanOrEqual(BigInteger x, BigInteger y)
        {
            return (LessThan(x, y) || Equals(x, y));
        }

        public static bool GreaterThan(BigInteger x, BigInteger y)
        {
            throw new NotImplementedException();
        }

        public static bool GreaterThanOrEqual(BigInteger x, BigInteger y)
        {
            return (GreaterThan(x, y) || Equals(x, y));
        }

        public static bool Equals(BigInteger x, BigInteger y)
        {
            if (x._Sign == y._Sign && x.Length == y.Length)
            {
                int len = x.Length;
                for (int i = 0; i < len; i++)
                {
                    if (x.m_Data[i] != y.m_Data[i]) break;
                }
                return true;
            }
            return false;
        }

        public static int Compare(BigInteger x, BigInteger y)
        {
            throw new NotImplementedException();
        }

        public int CompareTo(BigInteger other)
        {
            return Compare(this, other);
        }

        private static bool IsWhiteSpace(char ch)
        {
            return ((ch == ' ') || ((ch >= '\t') && (ch <= '\r')));
        }

        private static uint ParseSingleDigit(char c, ulong radix, out string error)
        {
            error = null;
            if ((c >= '0') && (c <= '9'))
            {
                return (uint)(c - '0');
            }
            if (radix == 0x10L)
            {
                c = (char)(c & '￟');
                if ((c >= 'A') && (c <= 'F'))
                {
                    return (uint)((c - 'A') + 10);
                }
            }
            error = "Invalid characters in string.";
            return uint.MaxValue;
        }

        #endregion

        public static BigInteger Parse(string strValue)
        {
            BigInteger ret;
            if (!TryParse(strValue, out ret))
            {
                throw new FormatException(strValue);

            }
            return ret;
        }

        public static bool TryParse(string strValue, out BigInteger result)
        {
            return TryParse(strValue, NumberStyles.None, null, out result);
        }

        public static bool TryParse(string strValue, NumberStyles style, IFormatProvider formatProvider, out BigInteger value)
        {
            string str;
            return TryParse(strValue, style, formatProvider, out value, out str);
        }

        private static bool TryParse(string s, NumberStyles style, IFormatProvider formatProvider, out BigInteger value, out string error)
        {
            if (s == null) throw new ArgumentNullException("s");
            if (formatProvider == null)
            {
                formatProvider = CultureInfo.CurrentCulture;
            }
            if ((style & ~(NumberStyles.HexNumber | NumberStyles.AllowLeadingSign)) != NumberStyles.None)
            {
                throw new NotSupportedException("Not supported number style.");
            }
            error = null;
            var format = (NumberFormatInfo)formatProvider.GetFormat(typeof(NumberFormatInfo));
            uint radix = (uint)(((style & NumberStyles.AllowHexSpecifier) != NumberStyles.None) ? 0x10 : 10);
            int charIndex = 0;
            bool signFlag = false;
            if ((style & NumberStyles.AllowLeadingWhite) != NumberStyles.None)
            {
                while ((charIndex < s.Length) && IsWhiteSpace(s[charIndex]))
                {
                    charIndex++;
                }
            }
            if ((style & NumberStyles.AllowLeadingSign) != NumberStyles.None)
            {
                int length = format.NegativeSign.Length;
                if (((length + charIndex) < s.Length) && (string.Compare(s, charIndex, format.NegativeSign, 0, length, false, CultureInfo.CurrentCulture) == 0))
                {
                    signFlag = true;
                    charIndex += format.NegativeSign.Length;
                }
            }
            value = Zero;
            BigInteger currentRadix = One;
            if (charIndex == s.Length)
            {
                error = "Parsed string was invalid.";
                return false;
            }
            for (int i = s.Length - 1; i >= charIndex; i--)
            {
                if (((style & NumberStyles.AllowTrailingWhite) != NumberStyles.None) && IsWhiteSpace(s[i]))
                {
                    int spaceIndex = i;
                    while (spaceIndex >= charIndex)
                    {
                        if (!IsWhiteSpace(s[spaceIndex])) break;
                        spaceIndex--;
                    }
                    if (spaceIndex < charIndex)
                    {
                        error = "Parsed string was invalid.";
                        return false;
                    }
                    i = spaceIndex;
                }
                uint currentDigit = ParseSingleDigit(s[i], radix, out error);
                if (error != null) return false;
                if (currentDigit != 0)
                {
                    value += currentDigit * currentRadix;
                }
                currentRadix *= radix;
            }
            if ((value._Sign == 1) && signFlag)
            {
                value = -(value);
            }
            return true;
        }

        public byte[] ToByteArray()
        {
            int dataLen = NumericHelper.GetLength(m_Data);
            byte[] result = new byte[dataLen * 4];
            Buffer.BlockCopy(m_Data, m_Data.Length - dataLen, result, 0, result.Length);
            return result;
        }

        #endregion

        #region Override
        public override bool Equals(object obj)
        {
            throw new NotImplementedException();
        }

        public override int GetHashCode()
        {
            if (Sign == SIGN_ZERO) return 0;
            throw new NotImplementedException();
        }

        public override string ToString()
        {
            throw new NotImplementedException();
        }

        #endregion

        #region Properties
        public int Sign
        {
            get { return _Sign; }
        }

        public int Length
        {
            get
            {
                return m_Data == null ? 0 : m_Data.Length;
            }
        }

        public static BigInteger One
        {
            get
            {
                return new BigInteger(1);
            }
        }

        public static BigInteger Zero
        {
            get
            {
                return new BigInteger(0, new uint[0]);
            }
        }


        #endregion

    }
}
